package com.lry.netty01.server.in;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.TooLongFrameException;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

@Slf4j
public class FrameDecoder extends ByteToMessageDecoder {
    //最大帧长
    private final int maxFrameLength;
//    engthFieldOffset：length字段的偏移量，initialBytesToStrip：跳过多少无用数据
//    例子1：
// tx.writeAndFlush(11);//发送数据前得把数据长度先发送过去
//ctx.writeAndFlush(“hello world”);
//    这种情况下lengthFieldOffset就是0，initialBytesToStrip是4
//    例子2：
//    int versionId = 1;
//ctx.writeAndFlush(versionId );
//ctx.writeAndFlush(11);//发送数据前得把数据长度先发送过去
//ctx.writeAndFlush(“hello world”);//真正数据
//    这种情况下lengthFieldOffset就是4(因为versionId 占4字节)，initialBytesToStrip是8
    private final int lengthFieldEndOffset;
    private final int lengthAdjustment;
    private final int initialBytesToStrip;

    private final boolean failFast;
    private boolean discardingTooLongFrame;
    private long tooLongFrameLength;
    private long bytesToDiscard;

    public FrameDecoder(int maxFrameLength) {
        this.maxFrameLength = maxFrameLength;
        this.lengthFieldEndOffset = 0;
        this.lengthAdjustment = 0;
        this.initialBytesToStrip = 0;
        this.failFast = false;
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if(in.readableBytes()<=0)
            return;

        Object decoded = this.decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    private void discardingTooLongFrame(ByteBuf in) {
        long bytesToDiscard = this.bytesToDiscard;
        int localBytesToDiscard = (int)Math.min(bytesToDiscard, (long)in.readableBytes());
        in.skipBytes(localBytesToDiscard);
        bytesToDiscard -= (long)localBytesToDiscard;
        this.bytesToDiscard = bytesToDiscard;
        this.failIfNecessary(false);
    }

    private static void failOnNegativeLengthField(ByteBuf in, long frameLength, int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException("negative pre-adjustment length field: " + frameLength);
    }

    private static void failOnFrameLengthLessThanLengthFieldEndOffset(ByteBuf in, long frameLength, int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException("Adjusted frame length (" + frameLength + ") is less than lengthFieldEndOffset: " + lengthFieldEndOffset);
    }

    private void exceededFrameLength(ByteBuf in, long frameLength) {
        long discard = frameLength - (long)in.readableBytes();
        this.tooLongFrameLength = frameLength;
        if (discard < 0L) {
            in.skipBytes((int)frameLength);
        } else {
            this.discardingTooLongFrame = true;
            this.bytesToDiscard = discard;
            in.skipBytes(in.readableBytes());
        }

        this.failIfNecessary(true);
    }

    private static void failOnFrameLengthLessThanInitialBytesToStrip(ByteBuf in, long frameLength, int initialBytesToStrip) {
        in.skipBytes((int)frameLength);
        throw new CorruptedFrameException("Adjusted frame length (" + frameLength + ") is less than initialBytesToStrip: " + initialBytesToStrip);
    }

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        if (this.discardingTooLongFrame) {
            this.discardingTooLongFrame(in);
        }

        if (in.readableBytes() < this.lengthFieldEndOffset) {
            return null;
        } else {
            int actualLengthFieldOffset = in.readerIndex();
            long frameLength = this.getUnadjustedFrameLength(in, actualLengthFieldOffset);
            if (frameLength < 0L) {
                failOnNegativeLengthField(in, frameLength, this.lengthFieldEndOffset);
            }

            frameLength += (long)(this.lengthAdjustment + this.lengthFieldEndOffset);
            if (frameLength < (long)this.lengthFieldEndOffset) {
                failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, this.lengthFieldEndOffset);
            }

            if (frameLength > (long)this.maxFrameLength) {
                this.exceededFrameLength(in, frameLength);
                return null;
            } else {
                int frameLengthInt = (int)frameLength;
                if (in.readableBytes() < frameLengthInt) {
                    return null;
                } else {
                    if (this.initialBytesToStrip > frameLengthInt) {
                        failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, this.initialBytesToStrip);
                    }

                    in.skipBytes(this.initialBytesToStrip);
                    int readerIndex = in.readerIndex();
                    int actualFrameLength = frameLengthInt - this.initialBytesToStrip;
                    ByteBuf frame = this.extractFrame(ctx, in, readerIndex, actualFrameLength);
                    in.readerIndex(readerIndex + actualFrameLength);
                    return frame;
                }
            }
        }
    }

    protected long getUnadjustedFrameLength(ByteBuf buf, int offset) {
        //固定包长度固定是7，我们还可以设计其他的包，在这里添加对应的逻辑
        if(buf.getByte(offset)==0X10){
            return 7;
        }else{
            log.info("frameDecoder: 数据包非法");
            //说明包有问题
            buf.clear();
        }
        return 0;
    }

    private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
        if (this.bytesToDiscard == 0L) {
            long tooLongFrameLength = this.tooLongFrameLength;
            this.tooLongFrameLength = 0L;
            this.discardingTooLongFrame = false;
            if (!this.failFast || firstDetectionOfTooLongFrame) {
                this.fail(tooLongFrameLength);
            }
        } else if (this.failFast && firstDetectionOfTooLongFrame) {
            this.fail(this.tooLongFrameLength);
        }

    }

    protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.retainedSlice(index, length);
    }

    private void fail(long frameLength) {
        if (frameLength > 0L) {
            throw new TooLongFrameException("Adjusted frame length exceeds " + this.maxFrameLength + ": " + frameLength + " - discarded");
        } else {
            throw new TooLongFrameException("Adjusted frame length exceeds " + this.maxFrameLength + " - discarding");
        }
    }
}
